import Foundation
class BenchmarkSuite {
    private var arrayTests: [(String, () -> Int)] = []
    private var cocosTests: [(String, () -> Int)] = []
    private var splayTests: [(String, () -> Int)] = []
    private var propertyAccess: [(String, () -> Int)] = []
    private var nobody: [(String, () -> Int)] = []
    private var functionCall: [(String, () -> Int)] = []
    private var numericCal: [(String, () -> Int)] = []
    
    private var showDetails = false
    
    init(showDetails: Bool = false) {
        self.showDetails = showDetails
    }
    func addTests(_ caseType: String, _ titile: String, _ testCase: @escaping() -> Int){
        if caseType=="Array Access"{
            arrayTests.append((titile, testCase))
        }else if caseType=="Cocos" {
            cocosTests.append((titile, testCase))
        }else if caseType=="Splay" {
            splayTests.append((titile, testCase))
        }else if caseType=="PropertyAccess" {
            propertyAccess.append((titile, testCase))
        }else if caseType=="Function Call" {
            functionCall.append((titile, testCase))
        }else if caseType=="Numerical Calculation" {
            numericCal.append((titile, testCase))
        }else if caseType=="No-Body" {
            nobody.append((titile, testCase))
        }
    }
    func initTests() {
        // functionCall.append(("Function Pointer No Parameters Call", runParameterlessFunctionPtr))
    }

    func runSingleGenera(genera: String, testCases: [(String, () -> Int)]) {
        for testCase in testCases {
            let suite = BenchmarkRunner(testCase.1)
            print("Benchmark - \(genera) - Swift- \(testCase.0) running: ")
            let finalScore = suite.computeScore(showDetails)
            print("Final Score: \(finalScore)")
        }
    }
    
    func runTests(){
        runSingleGenera(genera: "Array Access", testCases: arrayTests)
        runSingleGenera(genera: "Cocos", testCases: cocosTests)
        runSingleGenera(genera: "Splay", testCases: splayTests)
        runSingleGenera(genera: "PropertyAccess", testCases: propertyAccess)
        runSingleGenera(genera: "No-Body", testCases: nobody)
        runSingleGenera(genera: "Function Call", testCases: functionCall)
        runSingleGenera(genera: "Numerical Calculation", testCases: numericCal)
    }
    func runSingleTests(){
        print("NOTE: The Lower Score the Better Performance! :)")
        runTests()
    }
    func runAllTests() {
        print("NOTE: The Lower Score the Better Performance! :)")
        initTests()
        runTests()
    }
}

class BenchmarkRunner {
    // N is the total number of iteration times
    // default N value is 120
    private var N: Int = 120

    // M is the number of scores that are ranked last
    // default M value is 4
    private var M: Int = 4

    // allRecords is an Array records all running results
    // except the first result
    private var allRecords: [Int?]
    
    // worstRecords is an Array records M number of worst results
    private var worstRecords: [Int?]

    // initScore is the first score
    private var initScore: Int = 0

    // IterationScore is the average value of (N results - firstIteration)
    private var iterationScore: Double = 0

    // WorstCasesScore is the average value of M worstRecords excluding firstIteration
    private var worstCasesScore: Double = 0

    var target: () -> Int
    var testName:String? = nil
    init(testName:String? = nil, _ target: @escaping () -> Int, totalCount: Int? = nil, worstCount: Int? = nil) {
        self.target = target
        self.testName = testName
        if let totalCount = totalCount {
            self.N = totalCount
        }
        if let worstCount = worstCount {
            self.M = worstCount
        }
        self.allRecords = Array(repeating: 0, count: self.N)
        self.worstRecords = Array(repeating: 0, count: self.M)
    }

    func sampleAllData() {
        var sum = 0
        var data = 0
        for i in 0..<self.N {
            data = self.target()
            if i == 0 {
                self.initScore = data
                continue
            }
            self.allRecords[i] = data
            sum += data
        }
        self.iterationScore = Double(sum) / Double(self.N - 1)
        self.allRecords.sort(by: { $0! > $1! })

        sum = 0
        for j in 0..<self.M {
            self.worstRecords[j] = self.allRecords[j]
            sum += self.allRecords[j]!
        }
        self.worstCasesScore = Double(sum) / Double(self.M)
    }

    // Strategy:
    // Geometric Average of three scores
    func computeScore(_ showAllScore: Bool) -> Double {
        self.sampleAllData()
        let productsOfScores = Double(self.initScore) * self.iterationScore * self.worstCasesScore
        if showAllScore {
            print("InitScore: \(self.initScore)")
            print("IterationScore: \(self.iterationScore)")
            print("WorstCasesScore: \(self.worstCasesScore)")
        }
        return pow(productsOfScores, 1/3)
    }

    func getInitScore() -> Int {
        return self.initScore
    }

    func getIterationScore() -> Double {
        return self.iterationScore
    }

    func getWorstCasesScore() -> Double {
        return self.worstCasesScore
    }
    func run(){
        print("\(self.testName)   :   \(self.computeScore(false))   ms");
    }
}
